package spitfire.ksim.simulator;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;


/**
 * <p>
 * Abstract base class for simulated objects.
 * A simulated object is binded with a simulator, which act as a event-driven
 * engine to dispatch jobs for simulated objects. When a job of this object is 
 * dispatched by the simulator, {@link KSimObject#doJob(KJob)} will be invoked.
 * </p>
 * 
 * <p>
 * <i>The KSimObject keeps the initial jobs in this version, which will be 
 * removed in the future.</i>
 * </p>
 * 
 * <p>
 * This class also provides some facilities to get the simulation time of the
 * binded simulator, and to add job to the simulator for itself.
 * </p>
 * 
 * @see spitfire.ksim.simulator.KSimulator
 * @see spitfire.ksim.simulator.KJob
 * 
 * @author Adam
 *
 */
public abstract class KSimObject {
	
	private KSimulator simulator;
	private Collection<KJob> initialJobs = Collections
			.synchronizedList(new ArrayList<KJob>());
	private List<KOnDestroyListener> odLstnrList = Collections
			.synchronizedList(new ArrayList<KOnDestroyListener>());
	
	/**
	 * <p>
	 * Execute the given job. This method is invoked when a job of this object
	 * is dispatched by the simulator.
	 * </p>
	 * <p>
	 * <b>Note that variable accessed by this function must be synchronized
	 * in consideration of thread safety.</b> 
	 * </p>
	 * 
	 * @param job - The dispatched job for this object.
	 */
	public abstract void doJob(KJob job);
	
	/**
	 * <p>
	 * Returns the duration that the object is expected to be simulated.
	 * </p>
	 *<p>
	 * <b>Note that variable accessed by this function must be synchronized
	 * in consideration of thread safety.</b> 
	 * </p>
	 */
	public abstract KSimDuration getSimDuration();
	
	/**
	 * Construct a simulated object and bind it with the given simulator
	 * 
	 * @param simulator - the simulator to be binded with this object.
	 */
	public KSimObject(KSimulator simulator) {
		this.simulator = simulator;
		simulator.addSimObject(this);
	}
	
	/**
	 * Break up the binding with simulator. 
	 */
	public final void detachFromSimulator() {
		simulator.removeSimObject(this);
		simulator = null; 
	}
	
	/**
	 * @return the simulator that binded with this object.
	 */
	public final KSimulator getSimulator() {
		return simulator;
	}
	
	/**
	 * @return the initial jobs that will be added to the simulator when the
	 * simulator starts.
	 */
	public synchronized Collection<KJob> getInitialJobs() {
		return initialJobs;
	}
	
	/**
	 * Add an initial job to this object. 
	 * Initial job will be scheduled every time the simulator is started.</br>
	 * 
	 * @param job - the job to be added.
	 */
	public synchronized void addInitialJob(KJob job) {
		job.setJobExecutor(this);
		initialJobs.add(job);
		if (!simulator.isStopped()) {
			simulator.addJob(job, this);
		}
	}
	
	/**
	 * Remove all initial jobs. 
	 */
	public synchronized void clearInitialJobs() {
		initialJobs.clear();
	}
	
	/**
	 * Add job to the binded simulator for this object.
	 * @param job - the job to be added for this object.
	 */
	public void addOwnJob(KJob job) {
		simulator.addJob(job, this);
	}
	
	/**
	 * @return simulation time of the binded simulator.
	 */
	public long getSimTime() {
		return getSimulator().getSimTime();
	}
	
	public void addOnDestroyListener(KOnDestroyListener l) {
		odLstnrList.add(l);
	}

	public void removeOnDestroyListener(KOnDestroyListener l) {
		odLstnrList.remove(l);
	}

	public void removeAllOnDestroyListener() {
		odLstnrList.clear();
	}

	private void invokeOnDestroy() {
		ArrayList<KOnDestroyListener> copy = new ArrayList<KOnDestroyListener>(odLstnrList);
		for (KOnDestroyListener l : copy) {
			l.onDestroy();
		}
	}
	
	/**
	 * Destroy this object.
	 */
	public void destroy() {
		detachFromSimulator();
		invokeOnDestroy();
		removeAllOnDestroyListener();
	}
}